package com.prolixtech.jaminid;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import com.prolixtech.utils.SingletonLogger;
import com.prolixtech.utils.Suspender;

/**
 * As in most HTTP implementations, the Daemon opens a port, listens for
 * incoming connections, and then spawns the connections to serve that
 * connection. Sometimes this is called the server, we already have plenty of
 * things in the works that will be called servers, so we're going for the what
 * was the original name of the process before Marketting got to it.
 * 
 * We're going for a no frills implementation that handles all the law level
 * stuff and delegates the production of content to what we call a
 * ContentOracle. This is a nice clean design that makes this code reusable.
 * 
 * On the subject of why an HTTP server vs a JSP implementation, we find this to
 * be a better approach. Less messy, makes the whole thing easier to install,
 * more controllable, faster - this is no frills. The code is centralized,
 * almost all of it in the server itself.
 * 
 * @author Constantinos Michael
 * 
 * @modified by hachreak
 * 
 * TODO
 */
public class Daemon extends Thread {
    private int serverPort;

    private ServerSocket serverSocket;

    private Protocol activeProtocol = Protocol.Instance();

    private ContentOracle contentOracle;
    
    /**
     * Connection Listener
     * @author hachreak
     */
    private List<ConnectionListener> connListeners = new ArrayList<ConnectionListener>();

    
    public static final String VERSION = "0.2a";

    private boolean RUNNING = false;

	private Socket socket;

    /* some accessor fields */

    /**
     * returns the server port
     * 
     * @return the server port
     */
    public int getServerPort() {
        return this.serverPort;
    }

    /**
     * 
     * @return the protocol instance
     */
    public Protocol getProtocol() {
        return this.activeProtocol;
    }

    /**
     * 
     * @return the oracle instance
     */
    public ContentOracle getOracle() {
        return contentOracle;
    }

    /**
     * creates a new daemon and opens on specific port
     * 
     * @param serverPort
     *            the port to open on
     * @param cOra
     *            the content oracle to use
     *            
     * @modified by hachreak
     */
    public Daemon(int serverPort, ContentOracle cOra, ConnectionListener connList) {
        super();
        
        this.addConnectionListener(connList);
        
        this.setName("HTTP Daemon on " + this.serverPort);
        if (cOra == null) {
            this.printlog("USING STANDARD CONTENTORACLE");
            contentOracle = ContentOracle.Instance();
        } else {

            contentOracle = cOra;
        }
        printlog("Web Server Started, Logger Connected. Using: "
                + cOra.getClass());
        this.serverPort = serverPort;
        try {
            serverSocket = new ServerSocket(serverPort);            
            RUNNING = true;
            super.start();
        } catch (Exception e) {
            SingletonLogger.Instance().severe("A major error has occured: " + e);
            SingletonLogger.Instance().severe("Perhaps I could not bind port "
                    + serverPort + ". Exiting.");
            this.fireCloseConnectionListener();
            
            serverSocket = null;

        }

    }

    /**
     * creates a new daemon and opens on specific port
     * 
     * @param serverPort
     *            the port to open on
     * @param cOra
     *            the content oracle to use
     *            
     * @modified by hachreak
     */
    public Daemon(int serverPort, ContentOracle cOra) {
        super();
                
        this.setName("HTTP Daemon on " + this.serverPort);
        if (cOra == null) {
            this.printlog("USING STANDARD CONTENTORACLE");
            contentOracle = ContentOracle.Instance();
        } else {

            contentOracle = cOra;
        }
        printlog("Web Server Started, Logger Connected. Using: "
                + cOra.getClass());
        this.serverPort = serverPort;
        try {
            serverSocket = new ServerSocket(serverPort);            
            RUNNING = true;
            super.start();
        } catch (Exception e) {
            SingletonLogger.Instance().severe("A major error has occured: " + e);
            SingletonLogger.Instance().severe("Perhaps I could not bind port "
                    + serverPort + ". Exiting.");
            this.fireCloseConnectionListener();
            
            serverSocket = null;

        }

    }

    
    /**
     * Handles listening for new connections and launching client threads
     * 
     * 
     */
    public void run() {
        super.run();

        try {

            try {

                while (RUNNING) {
                    socket = serverSocket.accept();
                    this.fireOpenConnectionListener(socket.getRemoteSocketAddress().toString());
                    try {
                        printlog("Accepted Socket: " + socket);
                        new Connection(socket, this);
                    } catch (IOException e) {
                        socket.close();
                        this.fireCloseConnectionListener();
                    }
                } 

            } finally {
                serverSocket.close();
            }

        } catch (Exception e) {
            this.printlog("A major error has occured\n" + e);
            this.fireCloseConnectionListener();
            System.exit(-1);
        }

    }

    public String toString() {
        return ("mserve HTTPD daemon (" + this.getClass() + ") version "
                + VERSION + " running on port " + this.serverPort + ".");
    }

    public static void main(String args[]) {

        Daemon testServer = new Daemon(80, StreamingOracle.Instance());

    }

    public void printlog(String message) {
        SingletonLogger.Instance().fine(message);
    }

    /**
     * 
     * @return true if the server is up
     */
    public boolean isRunning() {
        return this.RUNNING;
    }

    /**
     * tears down server
     * @throws IOException 
     * 
     */
    public void tearDown() {
        RUNNING = false;
    }

    /**
     * Tears down server and waits for it to tear down
     * @throws IOException 
     * 
     */
    public void tearDownAndWait(){
        this.tearDown();
        while (RUNNING) {
            Suspender.suspendSeconds(1);
        }
        // TODO add error throw on time out
    }

    /**
     * Add connection listener
     * @param connList
     */
    public void addConnectionListener(ConnectionListener connList){
    	if(!connListeners.contains(connList))
    		connListeners.add(connList);
    }
    
    /**
     * Remove connection listener
     * @param connList
     */
    public void removeConnectionListener(ConnectionListener connList){
    	connListeners.remove(connList);
    }
    
    /**
     * Fire action: close
     * @author hachreak
     */
    private void fireCloseConnectionListener(){
    	Iterator<ConnectionListener> i = connListeners.iterator();
    	while(i.hasNext())
    		i.next().close();
    }
    
    /**
     * Fire action: open
     * @author hachreak
     */
    private void fireOpenConnectionListener(String server){
    	Iterator<ConnectionListener> i = connListeners.iterator();
    	while(i.hasNext())
    		i.next().open(server);
    }
}